#-*- coding utf-8 -*-
import time
import hashlib
import requests
import json
import datetime
import os
import multiprocessing
import logging

logger = logging.getLogger(__name__) # 返回一个新的以文件名为名的logger

def get_filePath_fileName_fileExt(filename):
    (file_path, temp_filename) = os.path.split(filename)
    (shot_name, extension) = os.path.splitext(temp_filename)
    return file_path, shot_name, extension


def split_by_size(fromfile, chunksize=50, todir=None):
    ochunksize=chunksize
    file_path, shot_name, extension = get_filePath_fileName_fileExt(fromfile)
    chunksize = chunksize*1024*1000

    if todir is None or todir.strip() == "":
        todir = file_path

    if not os.path.exists(todir):#check whether todir exists or not
        os.mkdir(todir)
    partnum = 0
    inputfile = open(fromfile,'rb')#open the fromfile
    files=[]
    while True:
        chunk = inputfile.read(chunksize)
        if not chunk:             #check the chunk is empty
            break
        partnum += 1
        filename = os.path.join(todir,('%s-bysize-%dM-%04d'%(shot_name,ochunksize,partnum)))
        fileobj = open(filename,'wb')#make partfile
        fileobj.write(chunk)
        files.append(filename)
        fileobj.close()
    return files

#按行数分隔
def split_by_row(fromfile,line=100,dir=None):
    filepath, shotname, extension = get_filePath_fileName_fileExt(fromfile)
    files=[]
    with open(fromfile) as myfile:
        index = 0
        while True:
            index += 1
            try:
                new_file='%s-byline-%d-%04d' % (shotname,line,index)
                new_file = dir+'/'+new_file
                files.append(new_file)
                with open(new_file, 'w') as f:
                    for _ in range(line):
                        f.write(myfile.next())
            except StopIteration:
                break
    return files


def formatGMTime():
    GMT_FORMAT =  '%a, %d %b %Y %H:%M:%S GMT'
    return datetime.datetime.utcnow().strftime(GMT_FORMAT)


class MultiuploadUploadPartError(Exception):
    """
    上传分片的时候出错
    """

    def __init__(self, message):
        """
        :param message: 错误信息
        """
        self.message = message

    def __str__(self):
        return repr(self.message)


class MultiuploadInitError(Exception):
    """
    分片上传初始化错误
    """
    def __init__(self, message):
        """
        :param message: 错误信息
        """
        self.message = message

    def __str__(self):
        return repr(self.message)


class MultiuploadCompleteError(Exception):
    """
    分片上传完成时错误
    """
    def __init__(self, message):
        """
        :param message: 错误信息
        """
        self.message = message

    def __str__(self):
        return repr(self.message)


class OSSClient(object):
    def __init__(self, app_domain, auth_domain, app_id, app_key, bucket):
        assert app_domain is not None and app_domain.strip() != "", "app_domain 不能为空"
        assert auth_domain is not None and auth_domain.strip() != "", "auth_domain 不能为空"
        assert app_id is not None and app_id.strip() != "", "app_id 不能为空"
        assert app_key is not None and app_key.strip() != "", "app_key 不能为空"
        assert bucket is not None and bucket.strip() != "", "bucket不能为空"
        self.app_domain = app_domain
        self.auth_domain = auth_domain
        self.app_id = app_id
        self.app_key = app_key
        self.bucket = bucket

    def createCert(self):
        """
        oss使用时证书获取
        :return:证书
        """
        ts = str(int(time.time()))
        signature_str = self.app_id + ts + self.app_key
        signature = hashlib.md5(signature_str.encode('utf-8')).hexdigest()
        params = {
            'appid': self.app_id,
            'ts': ts,
            'signature': signature
        }
        url = self.app_domain + "/v1/appmanager/certification/create"
        r = requests.post(url=url, data=json.dumps(params))
        res = json.loads(r.text)
        if res["code"] == 200:
            return res["data"]["certification"]
        else:
            raise AssertionError(res["msg"])

    def upload(self, object_name, object_path):
        """
        简单文件上传
        :param object_name: 对象名称
        :param object_path: 文件路径
        :return:
        """
        # cert = self.createCert()
        # headers = {
        #     'Content-Type': 'binary/octet-stream',
        #     'Certification': cert,
        #     'x-amz-date': formatGMTime(),
        #     'x-amz-acl': 'public-read-write',
        # }

        # url = self.auth_domain + "/v1/oss/object/upload/" + self.app_id + "/" + self.bucket + "/" + object_name
        # url = self.auth_domain + "/" + self.bucket + "/" + object_name
        url = self.auth_domain + "/" + object_name
        with open(object_path, 'rb') as f:
            data = f.read()
            r = requests.put(url, data=data)
            if r.reason != 'OK':
                raise AssertionError(r.reason)
        return True

    def download(self, object_name, download_path):
        """
        下载文件
        :param object_name: 对象名称
        :param download_path: 下载路径
        :return:
        """
        # cert = self.createCert()
        # headers = {
        #     "x-amz-date": formatGMTime(),
        #     "Certification": cert,
        # }
        # url = self.auth_domain + "/v1/oss/object/download/" + self.app_id + "/" + self.bucket + "/" + object_name
        # url = self.auth_domain + "/" + self.bucket + "/" + object_name
        url = self.auth_domain + "/" + object_name
        r = requests.get(url)
        if r.reason == 'OK':
            with open(download_path, "wb") as f:
                f.write(r.content)
        else:
            raise AssertionError(r.reason)

    def getPresignedUrl(self, object_name):
        """
        获取对象下载链接路径
        :param object_name:
        :return:
        """
        cert = self.createCert()
        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "object": object_name,
            "acl": 'public-read-write',
            "expire": -1,
            "certification": cert
        }
        url = self.app_domain + "/v1/appmanager/object/get_presignedurl"
        r = requests.post(url=url, data=json.dumps(params))
        if r.reason == 'OK':
            res = json.loads(r.text)
            url = res["data"]["url"]
            return url
        else:
            raise AssertionError(r.reason)

    def multiupload_init(self, object_name, cert=None):
        """
        分片上传初始化， cert 默认为None, 如果为None或者空会重新获取
        :param object_name: 对象名称
        :param cert: 证书
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        headers = {
            "x-amz-acl": "public-read-write",
        }

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "object": object_name,
            "certification": cert
        }
        url = self.app_domain + "/v1/appmanager/multiupload/init"
        r = requests.post(url, data=json.dumps(params),  headers=headers)
        if r.reason == 'OK':
            res = json.loads(r.text)
            if res["code"] == 200:
                upload_id = res["data"]["uploadid"]
                return upload_id
            else:
                raise MultiuploadInitError(res["data"])
        else:
            raise MultiuploadInitError(r.reason)

    def multiupload_upload(self, object_name, object_part_path, files_num, upload_id,cert=None):
        """
        上传分片
        :param object_name: 对象名称
        :param object_path_path:  对象分片名称
        :param files_num:  文件Id
        :param upload_id:  分片上传Id
        :param cert:  oss使用证书
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        url = self.auth_domain + "/v1/oss/object/uploadpart/" + self.app_id + "/" + self.bucket +"/" + object_name \
                        + "?partNumber="+str(files_num)+"&uploadId="+upload_id
        file_size = os.path.getsize(object_part_path)

        headers = {
            'Content-Type': 'binary/octet-stream',
            'Certification': cert,
            'x-amz-date': formatGMTime(),
            'x-amz-acl': 'public-read-write',
            'content-length': str(file_size)
        }

        result = {}
        with open(object_part_path, 'rb') as f:
            data = f.read()
            r = requests.put(url, data=data, headers=headers)
            if r.reason == 'OK':
                result = {
                    "partid": str(files_num),
                    "size": file_size,
                    "etag": r.headers.get("ETag")
                }
            else:
                raise MultiuploadUploadPartError("分片上传时出错了")
        return result

    def multiupload_complete(self, object_name, upload_id, parts, cert=None):
        """
        分片上传完成
        :param object_name: 对象名称
        :param upload_id:  分片上传ID
        :param parts:  分片上传list, list数组，包含每一个分片的etag, id, size
        :param cert:
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        url = self.app_domain + "/v1/appmanager/multiupload/complete"

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "certification": cert,
            "object": object_name,
            "uploadid": upload_id,
            "parts": parts
        }

        r = requests.post(url, json.dumps(params))
        if r.reason == "OK":
            result = json.loads(r.text)
            if result["msg"] != "ok":
                raise AssertionError("分片上传文件 "+object_name+" 失败, 原因为: " + str(result["data"]))
        else:
            raise MultiuploadCompleteError("分片上传文件 " + object_name + " 失败 , 原因为: " + r.reason)


    def multiupload_abort(self, object_name, upload_id, cert=None):
        """
        放弃分片上传
        :param object_name: 对象名称
        :param upload_id: 分片上传Id
        :param cert: oss使用证书
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "object": object_name,
            "certification": cert,
            "uploadid": upload_id
        }

        url = self.app_domain + "/v1/appmanager/multiupload/abort"
        r = requests.post(url, data=json.dumps(params))
        if r.reason == "OK":
            res = json.loads(r.text)
            return res["msg"]
        else:
            raise AssertionError(r.reason)

    def multiupload_list(self, object_name, uploadid, cert=None):
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "object": object_name,
            "certification": cert,
            "uploadid": uploadid
        }

        url = self.app_domain + "/v1/appmanager/multiupload/list"
        r = requests.post(url, data=json.dumps(params))
        if r.reason == "OK":
            res = json.loads(r.text)
            if res["code"] == 200:
                return res["data"]["parts"]
            else:
                raise AssertionError("获取 对象名称: " + object_name + " upload_id: " + uploadid + " 分片上传列表失败, 失败原因为: " + res["msg"])
        else:
            raise AssertionError("获取 对象名称: "+object_name+" upload_id: " + uploadid + " 分片上传列表失败, 失败原因为: " + r.reason)


    def multiupload_validate(self, upload_results, upload_list):
        """
        验证分片上传结果
        :param upload_result: upload
        :param upload_list:
        :return:
        """
        for r in upload_results:
            for l in upload_list:
                if r["partid"] == l["partid"]:
                    if r["etag"] == l["etag"] and r["size"] == l["size"]:
                        break;
                    else:
                        return False
        return True

    def multiupload(self, object_name, object_path, threads_num=8):
        """
        分片上传
        :param object_name: 对象名称
        :param object_path: 对象路径
        :param threads_num: 线程数量, 默认为8
        :return:
        """
        files = split_by_size(object_path)
        files_num = len(files)
        cert = self.createCert()
        upload_id = self.multiupload_init(object_name=object_name, cert=cert)

        process_result = []

        if threads_num < 1:
            threads_num = 8
        pool = multiprocessing.Pool(processes=threads_num)
        try:
            for i in range(files_num):
                object_part_path = files[i]
                process_result.append(pool.apply_async(self.multiupload_upload, (object_name, object_part_path, i+1, upload_id,cert)))
        except MultiuploadUploadPartError as e:
            logging.error(e.message)
            pool.close()
            pool.join()
            self.multiupload_abort(object_name, upload_id=upload_id, cert=cert)
            raise e

        pool.close()
        pool.join()
        upload_result = []
        for p in process_result:
            upload_result.append(p.get())

        if len(upload_result) != files_num:
            logging.error("上传文件数量和总文件数量不相符")
            self.multiupload_abort(object_name, upload_id=upload_id, cert=cert)
            return False

        upload_list = self.multiupload_list(object_name, uploadid=upload_id, cert=cert)
        if not self.multiupload_validate(upload_result, upload_list):
            logging.error("文件校验失败,放弃上传")
            self.multiupload_abort(object_name, upload_id=upload_id, cert=cert)
            return False

        try:
            self.multiupload_complete(object_name, upload_id, upload_result, cert=cert)
            return True
        except MultiuploadCompleteError as e:
            logging.error(e.message)
            self.multiupload_abort(object_name, upload_id=upload_id, cert=cert)
            raise e

        self.multiupload_abort(object_name, upload_id=upload_id, cert=cert)
        return False

    def delete(self, object_name, cert=None):
        """
        删除oss对象
        :param object_name:  对象名称:
        :param cert:  证书, 默认为空, 如果为空重新创建
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "object": object_name,
            "certification": cert,
        }
        url = self.app_domain + "/v1/appmanager/object/delete"

        r = requests.post(url, data=json.dumps(params))
        if r.reason == "OK":
            res = json.loads(r.text)
            if res["code"] == 200:
                return res["msg"]
            else:
                raise AssertionError("删除文件失败, 错误信息为: " + res["msg"])
        else:
            raise AssertionError("删除文件失败, 错误信息为: "+ r.reason)

    def delete_list(self, object_name_list, cert=None):
        """
        批量删除在oss上对象
        :param object_name_list:
        :param cert:
        :return:
        """
        if cert is None or cert.strip() == "":
            cert = self.createCert()

        url = self.app_domain + "/v1/appmanager/object/batchdelete"

        params = {
            "appid": self.app_id,
            "bucket": self.bucket,
            "objects": object_name_list,
            "certification": cert,
        }
        r = requests.post(url, data=json.dumps(params))
        if r.reason == "OK":
            res = json.loads(r.text)
            if res["code"] == 200:
                return res["data"]
            else:
                raise AssertionError("删除文件失败, 错误信息为: " + res["msg"])
        else:
            raise AssertionError("删除文件失败, 错误信息为: " + r.reason)

def test():
    oss_client = OSSClient(app_domain = 'http://10.17.157.85:9000',
                            auth_domain = 'http://10.17.157.85:9000',
                           app_id = 'admin',
                           app_key = 'admin123456',
                           bucket = 'meicloud-ai-platform/data/zjw');
    # oss_client = OSSClient(app_domain='http://oss-cn-foshan.meicloud.com:8000',
    #                        auth_domain='http://oss-cn-foshan.meicloud.com:17480',
    #                        app_id='88722E700EC94EE7AD143782896A9432',
    #                        app_key='DSV6vhpO2Uh3PGPi2eJ4O2Ni6NdAb9YBrVNYQPBq',
    #                        bucket='meicloud-ai-paltfrom');
    #oss_client.upload("md5.txt", "D:/MyData/zhouzx11/Downloads/md5.txt")
    #oss_client.download("md5.txt", "D:/MyData/zhouzx11/Downloads/md52.txt")
    #url = oss_client.getPresignedUrl("md5.txt")
    #print(url)
    # oss_client.multiupload("StackNet-master.zip", "D:/MyData/zhouzx11/Downloads/StackNet-master.zip")
    #uploadid = oss_client.multiupload_init("md5.txt", " ")
    # oss_client.delete_list(["StackNet-master.zip"])
    oss_client.download("meicloud-ai-platform/data/zjw"+"model/13231686233628549131.sav", "D:/svnres/ai开放平台/download/13231686233628549131.sav")
    print('文件下载成功')

if __name__ == "__main__":
    test()